05. Quiz: FFT

Fast Fourier Transforms

An FFT, or Fast Fourier Transform, is an efficient implementation of a Discrete Fourier Transform. The algorithm transforms a sum of sinusoidal signals into into its pure frequency components. In the following quiz, we will demonstrate adding sinusoidal waves together and then deconstructing them back into their component frequencies.

Instructions

choose_frequencies

In the first definition, choose_frequencies, choose three frequencies in a range from 1 to 50. These are the number of full cycles each sinusoidal wave will have in one "time unit". Running the definition will produce a visual of the three chosen frequencies as well as a new wave that is the sum of the three. This is similar to an acoustic signal, which is the sum of many sinusoidal waves. The waves may have different phases and amplitudes as well. Here's an example of a choice of (3, 8, 1) for the three frequencies.

add_the_waves

The second definition is where the waves are created. For this demonstration, we will only create three. This has been done for you with the utils.make_waves function - take a look at to understand how it works. You just need to add them together. This simulates an audio signal, which is really just sinusoidal waves added together. In audio signals, the sinusoidal waves are created by sound vibrations and may be at varying amplitudes and phase as well as frequency. To simulate this variety, the utils.make_waves function provides random amplitudes and phase shifts.

demo_fft

An FFT can be created with a variety of library functions including scipy.fftpack.fft , which we'll use in this quiz. Read the linked reference to understand how to use it in code.

This FFT algorithm will create both positive and negative values, but we'll just display the positive ones. When you're done, you should see something like the following, showing peaks at the three frequency values originally provided!

Start Quiz:

import numpy as np
import scipy.fftpack
import utils as utils

def choose_frequencies():
    """
    # provide three frequencies in a range between 1 and 50    
    :return: [int, int, int]
    """
    # *** TODO provide three frequencies between 1 and 50
    freq1 = None
    freq2 = None
    freq3 = None
    # end TODO

    return [freq1, freq2, freq3]


def add_the_waves(freqs):
    """
    create three sinusoidal waves and one wave that is the sum of the three
    :param freqs: [int, int, int]
    :return: [np.array, np.array, np.array, np.array]
        representing wave1, wave2, wave3, sum of waves
        each array contains 500(by default) discrete values for plotting a sinusoidal
    """
    _, _, t = utils.get_wave_timing()
    w1, w2, w3 = utils.make_waves(t, freqs)

    # TODO sum the waves together to form sum_waves
    sum_waves = None
    # end TODO

    return [w1, w2, w3, sum_waves]


def demo_fft(sum_waves):
    num_samples, spacing, _ = utils.get_wave_timing()

    # TODO create a Fast Fourier Transform of the waveform using scipy.fftpack.fft
    # named 'y_fft'
    y_fft = None
    # end TODO

    x_fft = np.linspace(0.0, 1.0/spacing, num_samples)
    return x_fft, y_fft
import matplotlib.pyplot as plt
import numpy as np

def sinusoid(freq):
    """
    return a sinusoidal of random amplitude and phase for a given frequency
    :param freq: 
    :return: 
    """
    phase = np.random.random()
    amplitude = 2 * (np.random.random_integers(1, 10))
    return amplitude * np.cos(2 * np.pi * freq - phase)


def get_wave_timing(num_samples=500, range_of_time = 5.0):
    """
    provide an array of time values of size num_samples spread evenly over range_of_time
    :param num_samples: int 
    :param range_of_time: float
    :return: int, float, np.array
    """
    # sample spacing
    spacing = range_of_time / num_samples
    # array for time samples
    t = np.linspace(0.0, range_of_time, num_samples)
    return num_samples, spacing, t


def make_waves(t, freqs):
    """
    convert three frequencies into arrays of discrete values representing sinusoidal waves
    :param freqs: [float, float, float]
    :return: [np.array, np.array, np.array]
    """
    w0 = sinusoid(t * freqs[0])
    w1 = sinusoid(t * freqs[1])
    w2 = sinusoid(t * freqs[2])
    return w0, w1, w2


# def display_sinusoids(time_array, f1, f2, f3, sum):
#     # plot three frequencies with random phase shifts on y axis
#     # plt.figure()
#     fig, ax = plt.subplots(4, 1)
#     # plt.subplot(411)  # 3 rows, 1 column, fignum 1
#     ax[0].plot(time_array, f1)
#     ax[0].set_title('1st frequency component')

#     # plt.subplot(412)  # 3 rows, 1 column, fignum 2
#     ax[1].plot(time_array, f2)
#     ax[1].set_title('2nd frequency component')

#     # plt.subplot(413)  # 3 rows, 1 column, fignum 3
#     ax[2].plot(time_array, f3)
#     ax[2].set_title('3rd frequency component')

#     # sum
#     # plt.subplot(414)  # 3 rows, 1 column, fignum 4
#     ax[3].plot(time_array, sum, 'r')
#     ax[3].set_title('Sum of components')
#     ax[3].set_ylabel('amplitude')
#     ax[3].set_xlabel('time')

#     # adjust format of display to make room for titles
#     plt.subplots_adjust(
#         top=0.94,
#         bottom=0.11,
#         left=0.11,
#         right=0.97,
#         hspace=0.65,
#         wspace=0.2
#     )
#     # plt.show()
#     return fig


# def display_fft(xf, yf):
#     num_samples = np.shape(yf)[0]
#     fig, ax = plt.subplots()
#     ax.plot(xf, 2.0 / num_samples * np.abs(yf[:num_samples // 2]))
#     plt.title('Fast Fourier Transform')
#     plt.xlabel('frequency')
#     plt.ylabel('amplitude')
#     return fig
import numpy as np
import scipy.fftpack
import utils as utils

def choose_frequencies():
    """
    # provide three frequencies in a range between 1 and 50    
    :return: [int, int, int]
    """
    # *** TODO provide three frequencies between 1 and 50
    freq1 = 1
    freq2 = 5
    freq3 = 25
    # end TODO

    return [freq1, freq2, freq3]


def add_the_waves(freqs):
    """
    create three sinusoidal waves and one wave that is the sum of the three
    :param freqs: [int, int, int]
    :return: [np.array, np.array, np.array, np.array]
        representing wave1, wave2, wave3, sum of waves
        each array contains 500(by default) discrete values for plotting a sinusoidal
    """
    _, _, t = utils.get_wave_timing()
    w1, w2, w3 = utils.make_waves(t, freqs)

    # TODO sum the waves together to form sum_waves
    sum_waves = w1+w2+w3
    # end TODO

    return [w1, w2, w3, sum_waves]


def demo_fft(sum_waves):
    num_samples, spacing, _ = utils.get_wave_timing()

    # TODO create a Fast Fourier Transform of the waveform using scipy.fftpack.fft
    # named 'y_fft'
    y_fft = scipy.fftpack.fft(sum_waves)
    # end TODO

    x_fft = np.linspace(0.0, 1.0/spacing, num_samples)
    return x_fft, y_fft